﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IWMAG.Scripting.AST.ObjectModel;
using IWMAG.Scripting.Types;
using IWMAG.Scripting.AST.TerminalExpressions;
using IWMAG.Scripting.Interpreter;
using IWMAG.Scripting.Compiler.UserInfo;
using System.Reflection;

namespace IWMAG.Scripting.AST.Operators {
    internal class AssignmentOperator : CodeExpression {
        public CodeExpression LHS;
        public CodeExpression RHS;

        public AssignmentOperator(Compiler.Lexeme l, CodeExpression lval, CodeExpression val)
            : base(l) {
            LHS = lval;
            RHS = val;
        }

        public override void Verify(Helpers.CompileTable sym, List<Compiler.IUserInfo> error) {
            sym.OpenScope();

            sym.Lookahead[typeof(NextIndexOperator)] = () => null;
            LHS.Verify(sym, error);

            sym.Lookahead.Remove(typeof(NextIndexOperator));

            CodeType t = sym.Dealias(LHS.TypeOf());

            sym.Lookahead[typeof(ValueExpression)] = () => t;

            RHS.Verify(sym, error);

            if (!t.Equals(sym.Dealias(RHS.TypeOf()))) {
                SendError(error, Rules.IncompatibleTypesInAssignment, RHS.TypeOf().ToString(), LHS.TypeOf().ToString());
            }
            sym.CloseScope();

            if (!(LHS is MemberOperator || LHS is IndexOperator ||
                LHS is VariableExpression || LHS is NextIndexOperator)) {
                SendError(error, Rules.NonLValueInAssignmentAsAssignee);
            }
        }

        public override CodeType TypeOf() {
            return LHS.TypeOf();
        }

        public override IEnumerable<Continuation> ValueOf(Interpreter.ScriptEngine engine, Helpers.RuntimeTable sym, Interpreter.Reference<Interpreter.RunValue> val) {
            if (LHS is VariableExpression) {
                RunValue var = sym.Variables[(LHS as VariableExpression).Name];
                foreach (Continuation wait in RHS.ValueOf(engine, sym, val)) yield return wait;

                var.Value = val.ReferencedValue;
            } else if (LHS is IndexOperator || LHS is NextIndexOperator) {
                Stack<int> stack = new Stack<int>();
                CodeExpression left = LHS is NextIndexOperator ? (LHS as NextIndexOperator).GHS : LHS;
                if (LHS is NextIndexOperator) {
                    stack.Push(-1);
                }

                Reference<RunValue> value = RunValue.NoValue;
                while (left is IndexOperator) {
                    IndexOperator index = left as IndexOperator;
                    foreach (Continuation wait in index.RHS.ValueOf(engine, sym, value)) yield return wait;
                    stack.Push((int)value.ReferencedValue.IntValue);
                    left = index.LHS;
                }

                foreach (Continuation wait in left.ValueOf(engine, sym, value)) yield return wait;

                ArrayRunValue array = value.ReferencedValue.ArrayValue;
                while (stack.Count > 1) {
                    array = array[stack.Pop()].ArrayValue;
                }

                foreach (Continuation wait in RHS.ValueOf(engine, sym, value)) yield return wait;
                array[stack.Pop()] = value.ReferencedValue;
            } else if (LHS is MemberOperator) {
                Stack<MemberOperator> stack = new Stack<MemberOperator>();
                CodeExpression left = LHS;
                while (left is MemberOperator) {
                    stack.Push(left as MemberOperator);
                    left = (left as MemberOperator).Object;
                }

                Reference<RunValue> obj = RunValue.NoValue;
                foreach (Continuation wait in left.ValueOf(engine, sym, obj)) yield return wait;

                object cur = obj.ReferencedValue.Value;
                while (stack.Count > 1) {
                    MemberOperator op = stack.Pop();
                    if (op.IsProperty) {
                        cur = op.ClrType.GetProperty(op.MemberName).GetValue(cur, null);
                    } else if (op.IsField) {
                        cur = op.ClrType.GetField(op.MemberName).GetValue(cur);
                    } else {
                        throw new NotImplementedException();
                    }
                }

                foreach (Continuation wait in RHS.ValueOf(engine, sym, val)) yield return wait;

                MemberOperator set = stack.Pop();
                if (set.IsProperty) {
                    PropertyInfo prop = set.ClrType.GetProperty(set.MemberName);
                    prop.SetValue(cur, val.ReferencedValue.Value, null);
                } else if (set.IsField) {
                    FieldInfo field = set.ClrType.GetField(set.MemberName);
                    field.SetValue(cur, val.ReferencedValue.Value);
                } else {
                    throw new NotImplementedException();
                }
            } else {
                throw new NotImplementedException();
            }
            yield break;
        }
    }
}